package cn.hy.limiter;

import static java.lang.Math.min;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 *
 *  https://www.javadoop.com/post/rate-limiter
 * <br>
 *  SmoothRateLimiter
 *
 *   SmoothBursty
 *
 *    Bursty 是突发的意思，它说的不是下面这个意思：我们设置了 1k 每秒，而我们可以一次性获取 5k 的 permits，这个场景表达的不是突发，
 *    而是在说预先占有了接下来几秒产生的 permits。希望大家不要被误导了。
 *
 *    突发说的是，RateLimiter 会缓存一定数量的 permits 在池中，这样对于突发请求，
 *    能及时得到满足。想象一下我们的某个接口，很久没有请求过来，突然同时来了好几个请求，
 *    如果我们没有缓存一些 permits 的话，很多线程就需要等待了。
 *
 *    1. SmoothBursty 默认缓存最多 1 秒钟的 permits，不可以修改。
 *
 *
 *  SmoothWarmingUp
 *    SmoothWarmingUp 适用于资源需要预热的场景，比如我们的某个接口业务，需要使用到数据库连接，
 *    由于连接需要预热才能进入到最佳状态，如果我们的系统长时间处于低负载或零负载状态（当然，应用刚启动也是一样的）
 *    ，连接池中的连接慢慢释放掉了，此时我们认为连接池是冷的。
 *    假设我们的业务在稳定状态下，正常可以提供最大 1000 QPS 的访问，但是如果连接池是冷的，
 *    我们就不能让系统达到 1000 个 QPS，要限制住突发流量，因为这会拖垮我们的系统，我们应该有个预热升温的过程。
 *    对应到 SmoothWarmingUp 中，如果系统处于低负载状态，storedPermits 会一直增加，当请求来的时候，
 *    我们要从 storedPermits 中取 permits，最关键的点在于，从 storedPermits 中取 permits 的操作是比较耗时的，因为没有预热。
 *
 *
 *   SmoothBursty，它从 storedPermits 中获取 permits 是不需要等待时间的，因为它是存货，
 *   而这边洽洽相反，从 storedPermits 获取需要更多的时间，这是最大的不同，先理解这一点，能帮助你更好地理解源码。
 *
 *
 * </br>
 * @author zhaomu
 */
public class GuavaRateLimiter {


    // stopwatch 非常重要，它用来“计时”，RateLimiter 把实例化的时间设置为 0 值，后续都是取相对时间，用微秒表示。
    // private final SleepingStopwatch stopwatch;

    /**
     * mutexDoNotUseDirectly 用来做锁，RateLimiter 依赖于 synchronized 来控制并发，
     * 所以我们之后可以看到，各个属性甚至都没有用 volatile 修饰。
     */
    private volatile Object mutexDoNotUseDirectly;



    // 当前还有多少 permits 没有被使用，被存下来的 permits 数量
    double storedPermits;

    // 最大允许缓存的 permits 数量，也就是 storedPermits 能达到的最大值
    double maxPermits;

    // 每隔多少时间产生一个 permit，
    // 比如我们构造方法中设置每秒 5 个，也就是每隔 200ms 一个，这里单位是微秒，也就是 200,000
    double stableIntervalMicros;

    // 下一次可以获取 permits 的时间，这个时间是相对 RateLimiter 的构造时间的，是一个相对时间，理解为时间戳吧
    /**
     * nextFreeTicketMicros 是一个很关键的属性。
     *
     * 我们每次获取 permits 的时候，先拿 storedPermits 的值，因为它是存货，如果够，storedPermits 减去相应的值就可以了，
     * 如果不够，那么还需要将 nextFreeTicketMicros 往前推，表示我预占了接下来多少时间的量了。
     * 那么下一个请求来的时候，如果还没到 nextFreeTicketMicros 这个时间点，需要 sleep 到这个点再返回，
     * 当然也要将这个值再往前推。
     *
     * 大家在这里可能会有疑惑，因为时间是一直往前走的，应该要一直往池中添加 permits，
     * 所以 storedPermits 的值需要不断往上添加，难道需要另外开启一个线程来添加 permits？
     * 其实不是的，只需要在关键的操作中同步一下，重新计算就好了。
     */
    private long nextFreeTicketMicros = 0L;



    final void doSetRate(double permitsPerSecond, long nowMicros) {
        resync(nowMicros);
        double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
        this.stableIntervalMicros = stableIntervalMicros;
        doSetRate(permitsPerSecond, stableIntervalMicros);
    }


    void resync(long nowMicros) {
        // if nextFreeTicket is in the past, resync to now
        if (nowMicros > nextFreeTicketMicros) {
            double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
            storedPermits = min(maxPermits, storedPermits + newPermits);
            nextFreeTicketMicros = nowMicros;
        }
    }


     double coolDownIntervalMicros(){
        return  0.0;
     }

     void doSetRate(double permitsPerSecond, double stableIntervalMicros){}

}
